Skip to content

16 嫌三次握手太慢 - 来快速打开吧

前面几篇文章讲了三次握手的过程,可能你会有觉得好麻烦呀,要发数据先得有三次包交互建连。三次握手带来的延迟使得创建一个新 TCP 连接代价非常大,所有有了各种连接重用的技术。

但是连接并不是想重用就重用的,在不重用连接的情况下,如何减少新建连接代理的性能损失呢?

于是人们提出了 TCP 快速打开(TCP Fast Open,TFO),尽可能降低握手对网络延迟的影响。今天我们就讲讲这其中的原理。

01 TFO 与 shadowsocks

最开始知道 TCP Fast Open 是在玩 shadowsocks 时在它的 wiki 上无意中逛到的。专门有一页介绍可以启用 TFO 来减低延迟。原文摘录如下:

bash
If both of your server and client are deployed on Linux 3.7.1 or higher, you can turn on fast_open for lower latency.

First set fast_open to true in your config.json.

Then turn on fast open on your OS temporarily:

echo 3 > /proc/sys/net/ipv4/tcp_fastopen

02 TFO 简介

TFO 是在原来 TCP 协议上的扩展协议,它的主要原理就在发送第一个 SYN 包的时候就开始传数据了,不过它要求当前客户端之前已经完成过「正常」的三次握手。快速打开分两个阶段:请求 Fast Open Cookie 和 真正开始 TCP Fast Open

请求 Fast Open Cookie 的过程如下:

  • 客户端发送一个 SYN 包,头部包含 Fast Open 选项,且该选项的 Cookie 为空,这表明客户端请求 Fast Open Cookie
  • 服务端收取 SYN 包以后,生成一个 cookie 值(一串字符串)
  • 服务端发送 SYN + ACK 包,在 Options 的 Fast Open 选项中设置 cookie 的值
  • 客户端缓存服务端的 IP 和收到的 cookie 值

img

第一次过后,客户端就有了缓存在本地的 cookie 值,后面的握手和数据传输过程如下:

  • 客户端发送 SYN 数据包,里面包含数据和之前缓存在本地的 Fast Open Cookie。(注意我们此前介绍的所有 SYN 包都不能包含数据)
  • 服务端检验收到的 TFO Cookie 和传输的数据是否合法。如果合法就会返回 SYN + ACK 包进行确认并将数据包传递给应用层,如果不合法就会丢弃数据包,走正常三次握手流程(只会确认 SYN)
  • 服务端程序收到数据以后可以握手完成之前发送响应数据给客户端了
  • 客户端发送 ACK 包,确认第二步的 SYN 包和数据(如果有的话)
  • 后面的过程就跟非 TFO 连接过程一样了

img

03 抓包演示

上面说的都是理论分析,下面我们用实际的抓包来看快速打开的过程。

因为在 Linux 上快速打开是默认关闭的,需要先开启 TFO,如前面 shadowsocks 的文档所示

bash
echo 3 > /proc/sys/net/ipv4/tcp_fastopen

接下来用 nginx 来充当服务器,在服务器 c2 上安装 nginx,修改 nginx 配置 listen 80 fastopen=256;,使之支持 TFO

bash
server {
        listen 80  fastopen=256;
        server_name test.ya.me;
        access_log  /var/log/nginx/host.test.ya.me main;
        location /{
            default_type text/html;
            return 200 '<html>Hello, Nginx</html>';
        }
}

下面来调整客户端的配置,用另外一台 Centos7 的机器充当客户端(记为 c1),在我的 Centos7.4 系统上 curl 的版本比较旧,是 7.29 版本

bash
curl -V
curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.36 zlib/1.2.7 libidn/1.28 libssh2/1.4.3

这个版本的 curl 还不支持 TFO 选项,需要先升级到最新版本。升级的过程也比较简单,就分三步

bash
// 1. 增加 city-fan
rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-2-1.rhel7.noarch.rpm
// 2. 修改 city-fan.org.repo,把 enable=0 改为 enable=1
vim /etc/yum.repos.d/city-fan.org.repo
// 2. 升级 curl
yum update curl
// 验证是不是最新版本
curl -V
curl 7.64.1 (x86_64-redhat-linux-gnu) libcurl/7.64.1 NSS/3.36 zlib/1.2.7 libpsl/0.7.0 (+libicu/50.1.2) libssh2/1.8.2 nghttp2/1.31.1

下面就可以来演示快速打开的过程了。

第一次:请求 Fast Open Cookie

在客户端 c1 上用 curl 发起第一次请求,curl --tcp-fastopen http://test.ya.me,抓包如下图

img

逐个包分析一下

  • 第 1 个 SYN 包:wireshark 有标记 TFO=R,看下这个包的 TCP 首部

    img

    这个首部包含了 TCP Fast Open 选项,但是 Cookie 为空,表示向服务器请求新的 Cookie。

  • 第 2 个包是 SYN + ACK 包,wireshark 标记为 TFO=C,这个包的首部如下图所示

    img

    这时,服务器 c2 已经生产了一个值为 "16fba4d72be34e8c" 的 Cookie,放在首部的 TCP fast open 选项里

  • 第 3 个包是客户端 c1 对服务器的 SYN 包的确认包。到此三次握手完成,这个过程跟无 TFO 三次握手唯一的不同点就在于 Cookie 的请求和返回

  • 后面的几个包就是正常的数据传输和四次挥手断开连接了,跟正常无异,不再详细介绍。

第二次:真正的快速打开

在客户端 c1 上再次请求一次 curl --tcp-fastopen http://test.ya.me,抓包如下图

img

逐个包分析一下

  • 第 1 个包就很亮瞎眼,wireshark 把这个包识别为了 HTTP 包,展开头部看一下

    img

    这个包本质是一个 SYN 包,只是数据跟随 SYN 包一起发送,在 TCP 首部里也包含了第一次请求的 Cookie

  • 第 2 个包是服务端收到了 Cookie 进行合法性校验通过以后返回的 SYN + ACK 包

  • 第 3、4 个包分别是客户端回复给服务器的 ACK 确认包和服务器返回的 HTTP 响应包。因为我是在局域网内演示,延迟太小,ACK 回的太快了,所以看到的是先收到 ACK 再发送响应数据包,在实际情况中这两个包的顺序可能是不确定的。

04 TCP Fast Open 的优势

一个最显著的优点是可以利用握手去除一个往返 RTT,如下图所示

img

在开启 TCP Fast Open 以后,从第二次请求开始,就可以在一个 RTT 时间拿到响应的数据。

还有一些其它的优点,比如可以防止 SYN-Flood 攻击之类的

05 代码中是怎么使用的 Fast Open

用 strace 命令来看一下 curl 的过程

加上 –tcp-fastopen 选项以后的 strace 输出 sudo strace curl --tcp-fastopen http://test.ya.me 可以看到客户端没有使用 connect 建连,而是直接调用了 sendto 函数,加上了 MSG_FASTOPEN flag 连接服务端同时发送数据。

img

没有加上 –tcp-fastopen 选项的情况下的 strace 输出如下 sudo strace curl http://test.ya.me

img

在没有启用 Fast Open 的情况下,会先调用 connect 进行握手

06 小结

这篇文章主要用 curl 命令演示了 TCP 快速打开的详细过程和原理

  1. 客户端发送一个 SYN 包,头部包含 Fast Open 选项,且该选项的 Cookie 长度为 0
  2. 服务端根据客户端 IP 生成 cookie,放在 SYN+ACK 包中一同发回客户端
  3. 客户端收到 Cookie 以后缓存在自己的本地内存
  4. 客户端再次访问服务端时,在 SYN 包携带数据,并在头部包含 上次缓存在本地的 TCP cookie
  5. 如果服务端校验 Cookie 合法,则在客户端回复 ACK 前就可以直接发送数据。如果 Cookie 不合法则按照正常三次握手进行。

可以看到历代大牛在降低网络延迟方面的鬼斧神工般的努力,现在主流操作系统和浏览器都支持这个选项了。